using System;
using System.Text.RegularExpressions;
using System.Xml;
using ICSharpCode.TextEditor.Formatting.TSqlFormatter.Interfaces;

namespace ICSharpCode.TextEditor.Formatting.TSqlFormatter
{
    public class ParseTree : XmlDocument, IParseTree
    {
        private XmlElement _currentContainer;

        private bool _newStatementDue;

        internal XmlElement CurrentContainer
        {
            get
            {
                return _currentContainer;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("CurrentContainer");
                }
                if (!value.OwnerDocument.Equals(this))
                {
                    throw new Exception("Current Container node can only be set to an element in the current document.");
                }
                _currentContainer = value;
            }
        }

        internal bool NewStatementDue
        {
            get
            {
                return _newStatementDue;
            }
            set
            {
                _newStatementDue = value;
            }
        }

        internal bool ErrorFound
        {
            get
            {
                return _newStatementDue;
            }
            private set
            {
                if (value)
                {
                    base.DocumentElement.SetAttribute("errorFound", "1");
                }
                else
                {
                    base.DocumentElement.RemoveAttribute("errorFound");
                }
            }
        }

        public ParseTree(string rootName)
        {
            XmlElement xmlElement = CreateElement(rootName);
            AppendChild(xmlElement);
            CurrentContainer = xmlElement;
        }

        public XmlDocument ToXmlDoc()
        {
            return this;
        }

        internal void SetError()
        {
            CurrentContainer.SetAttribute("hasError", "1");
            ErrorFound = true;
        }

        internal XmlElement SaveNewElement(string newElementName, string newElementValue)
        {
            return SaveNewElement(newElementName, newElementValue, CurrentContainer);
        }

        internal XmlElement SaveNewElement(string newElementName, string newElementValue, XmlElement targetNode)
        {
            XmlElement xmlElement = CreateElement(newElementName);
            xmlElement.InnerText = newElementValue;
            targetNode.AppendChild(xmlElement);
            return xmlElement;
        }

        internal XmlElement SaveNewElementWithError(string newElementName, string newElementValue)
        {
            XmlElement result = SaveNewElement(newElementName, newElementValue);
            SetError();
            return result;
        }

        internal XmlElement SaveNewElementAsPriorSibling(string newElementName, string newElementValue, XmlElement nodeToSaveBefore)
        {
            XmlElement xmlElement = CreateElement(newElementName);
            xmlElement.InnerText = newElementValue;
            nodeToSaveBefore.ParentNode.InsertBefore(xmlElement, nodeToSaveBefore);
            return xmlElement;
        }

        internal void StartNewContainer(string newElementName, string containerOpenValue, string containerType)
        {
            CurrentContainer = SaveNewElement(newElementName, "");
            XmlElement targetNode = SaveNewElement("ContainerOpen", "");
            SaveNewElement("OtherKeyword", containerOpenValue, targetNode);
            CurrentContainer = SaveNewElement(containerType, "");
        }

        internal void StartNewStatement()
        {
            StartNewStatement(CurrentContainer);
        }

        internal void StartNewStatement(XmlElement targetNode)
        {
            NewStatementDue = false;
            XmlElement targetNode2 = SaveNewElement("SqlStatement", "", targetNode);
            CurrentContainer = SaveNewElement("Clause", "", targetNode2);
        }

        internal void EscapeAnyBetweenConditions()
        {
            if (PathNameMatches(0, "UpperBound") && PathNameMatches(1, "Between"))
            {
                MoveToAncestorContainer(2);
            }
        }

        internal void EscapeMergeAction()
        {
            if (PathNameMatches(0, "Clause") && PathNameMatches(1, "SqlStatement") && PathNameMatches(2, "MergeAction") && HasNonWhiteSpaceNonCommentContent(CurrentContainer))
            {
                MoveToAncestorContainer(4);
            }
        }

        internal void EscapePartialStatementContainers()
        {
            if (PathNameMatches(0, "DDLProceduralBlock") || PathNameMatches(0, "DDLOtherBlock") || PathNameMatches(0, "DDLDeclareBlock"))
            {
                MoveToAncestorContainer(1);
            }
            else if (PathNameMatches(0, "ContainerContentBody") && PathNameMatches(1, "CursorForOptions"))
            {
                MoveToAncestorContainer(3);
            }
            else if (PathNameMatches(0, "ContainerContentBody") && PathNameMatches(1, "PermissionsRecipient"))
            {
                MoveToAncestorContainer(3);
            }
            else if (PathNameMatches(0, "ContainerContentBody") && PathNameMatches(1, "DDLWith") && (PathNameMatches(2, "PermissionsBlock") || PathNameMatches(2, "DDLProceduralBlock") || PathNameMatches(2, "DDLOtherBlock") || PathNameMatches(2, "DDLDeclareBlock")))
            {
                MoveToAncestorContainer(3);
            }
            else if (PathNameMatches(0, "MergeWhen"))
            {
                MoveToAncestorContainer(2);
            }
            else if (PathNameMatches(0, "ContainerContentBody") && (PathNameMatches(1, "CTEWithClause") || PathNameMatches(1, "DDLDeclareBlock")))
            {
                MoveToAncestorContainer(2);
            }
        }

        internal void EscapeAnySingleOrPartialStatementContainers()
        {
            EscapeAnyBetweenConditions();
            EscapeAnySelectionTarget();
            EscapeJoinCondition();
            if (!HasNonWhiteSpaceNonCommentContent(CurrentContainer))
            {
                return;
            }
            EscapeCursorForBlock();
            EscapeMergeAction();
            EscapePartialStatementContainers();
            while (PathNameMatches(0, "Clause") && PathNameMatches(1, "SqlStatement") && PathNameMatches(2, "ContainerSingleStatementBody"))
            {
                XmlNode xmlNode = CurrentContainer.ParentNode.ParentNode;
                if (PathNameMatches(xmlNode, 1, "ElseClause"))
                {
                    CurrentContainer = (XmlElement)xmlNode.ParentNode.ParentNode.ParentNode;
                }
                else
                {
                    CurrentContainer = (XmlElement)xmlNode.ParentNode.ParentNode;
                }
            }
        }

        private void EscapeCursorForBlock()
        {
            if (PathNameMatches(0, "Clause") && PathNameMatches(1, "SqlStatement") && PathNameMatches(2, "ContainerContentBody") && PathNameMatches(3, "CursorForBlock") && HasNonWhiteSpaceNonCommentContent(CurrentContainer))
            {
                MoveToAncestorContainer(5);
            }
        }

        private XmlElement EscapeAndLocateNextStatementContainer(bool escapeEmptyContainer)
        {
            EscapeAnySingleOrPartialStatementContainers();
            if (PathNameMatches(0, "BooleanExpression") && (PathNameMatches(1, "IfStatement") || PathNameMatches(1, "WhileLoop")))
            {
                return SaveNewElement("ContainerSingleStatementBody", "", (XmlElement)CurrentContainer.ParentNode);
            }
            if (PathNameMatches(0, "Clause") && PathNameMatches(1, "SqlStatement") && (escapeEmptyContainer || HasNonWhiteSpaceNonSingleCommentContent(CurrentContainer)))
            {
                return (XmlElement)CurrentContainer.ParentNode.ParentNode;
            }
            return null;
        }

        private void MigrateApplicableCommentsFromContainer(XmlElement previousContainerElement)
        {
            XmlNode xmlNode = previousContainerElement;
            XmlNode xmlNode2 = previousContainerElement.LastChild;
            XmlElement refChild = CurrentContainer;
            while (xmlNode2 != null)
            {
                if (xmlNode2.NodeType == XmlNodeType.Whitespace || xmlNode2.Name.Equals("WhiteSpace"))
                {
                    xmlNode2 = xmlNode2.PreviousSibling;
                }
                else if (xmlNode2.PreviousSibling != null && (xmlNode2.Name.Equals("SingleLineComment") || xmlNode2.Name.Equals("SingleLineCommentCStyle") || xmlNode2.Name.Equals("MultiLineComment")) && (xmlNode2.PreviousSibling.NodeType == XmlNodeType.Whitespace || xmlNode2.PreviousSibling.Name.Equals("WhiteSpace") || xmlNode2.PreviousSibling.Name.Equals("SingleLineComment") || xmlNode2.PreviousSibling.Name.Equals("SingleLineCommentCStyle") || xmlNode2.PreviousSibling.Name.Equals("MultiLineComment")))
                {
                    if ((xmlNode2.PreviousSibling.NodeType == XmlNodeType.Whitespace || xmlNode2.PreviousSibling.Name.Equals("WhiteSpace")) && Regex.IsMatch(xmlNode2.PreviousSibling.InnerText, "(\\r|\\n)+"))
                    {
                        while (!xmlNode.LastChild.Equals(xmlNode2))
                        {
                            XmlElement xmlElement = (XmlElement)xmlNode.LastChild;
                            CurrentContainer.ParentNode.InsertBefore(xmlElement, refChild);
                            refChild = xmlElement;
                        }
                        CurrentContainer.ParentNode.InsertBefore(xmlNode2, refChild);
                        refChild = (XmlElement)xmlNode2;
                        xmlNode2 = xmlNode.LastChild;
                    }
                    else
                    {
                        xmlNode2 = xmlNode2.PreviousSibling;
                    }
                }
                else if (xmlNode2.NodeType == XmlNodeType.Text && !string.IsNullOrEmpty(xmlNode2.InnerText))
                {
                    xmlNode2 = null;
                }
                else
                {
                    xmlNode = xmlNode2;
                    xmlNode2 = xmlNode2.LastChild;
                }
            }
        }

        internal void ConsiderStartingNewStatement()
        {
            EscapeAnyBetweenConditions();
            EscapeAnySelectionTarget();
            EscapeJoinCondition();
            XmlElement currentContainer = CurrentContainer;
            XmlElement xmlElement = EscapeAndLocateNextStatementContainer(escapeEmptyContainer: false);
            if (xmlElement != null)
            {
                XmlElement currentContainer2 = CurrentContainer;
                StartNewStatement(xmlElement);
                if (!currentContainer2.Equals(currentContainer))
                {
                    MigrateApplicableCommentsFromContainer(currentContainer2);
                }
                MigrateApplicableCommentsFromContainer(currentContainer);
            }
        }

        internal void ConsiderStartingNewClause()
        {
            EscapeAnySelectionTarget();
            EscapeAnyBetweenConditions();
            EscapePartialStatementContainers();
            EscapeJoinCondition();
            if (CurrentContainer.Name.Equals("Clause") && HasNonWhiteSpaceNonSingleCommentContent(CurrentContainer))
            {
                XmlElement currentContainer = CurrentContainer;
                CurrentContainer = SaveNewElement("Clause", "", (XmlElement)CurrentContainer.ParentNode);
                MigrateApplicableCommentsFromContainer(currentContainer);
            }
            else if (CurrentContainer.Name.Equals("ExpressionParens") || CurrentContainer.Name.Equals("InParens") || CurrentContainer.Name.Equals("SelectionTargetParens") || CurrentContainer.Name.Equals("SqlStatement"))
            {
                CurrentContainer = SaveNewElement("Clause", "");
            }
        }

        internal void EscapeAnySelectionTarget()
        {
            if (PathNameMatches(0, "SelectionTarget"))
            {
                CurrentContainer = (XmlElement)CurrentContainer.ParentNode;
            }
        }

        internal void EscapeJoinCondition()
        {
            if (PathNameMatches(0, "ContainerContentBody") && PathNameMatches(1, "JoinOn"))
            {
                MoveToAncestorContainer(2);
            }
        }

        internal bool FindValidBatchEnd()
        {
            XmlElement xmlElement = EscapeAndLocateNextStatementContainer(escapeEmptyContainer: true);
            return xmlElement != null && (xmlElement.Name.Equals("SqlRoot") || (xmlElement.Name.Equals("ContainerContentBody") && xmlElement.ParentNode.Name.Equals("DDLAsBlock")));
        }

        internal bool PathNameMatches(int levelsUp, string nameToMatch)
        {
            return PathNameMatches(CurrentContainer, levelsUp, nameToMatch);
        }

        internal bool PathNameMatches(XmlNode targetNode, int levelsUp, string nameToMatch)
        {
            XmlNode xmlNode = targetNode;
            while (levelsUp > 0)
            {
                xmlNode = xmlNode.ParentNode;
                levelsUp--;
            }
            return xmlNode.Name.Equals(nameToMatch);
        }

        private static bool HasNonWhiteSpaceNonSingleCommentContent(XmlElement containerNode)
        {
            foreach (XmlElement item in containerNode.SelectNodes("*"))
            {
                if (!item.Name.Equals("WhiteSpace") && !item.Name.Equals("SingleLineComment") && !item.Name.Equals("SingleLineCommentCStyle") && (!item.Name.Equals("MultiLineComment") || Regex.IsMatch(item.InnerText, "(\\r|\\n)+")))
                {
                    return true;
                }
            }
            return false;
        }

        internal bool HasNonWhiteSpaceNonCommentContent(XmlElement containerNode)
        {
            foreach (XmlElement item in containerNode.SelectNodes("*"))
            {
                if (!IsCommentOrWhiteSpace(item.Name))
                {
                    return true;
                }
            }
            return false;
        }

        internal static bool IsCommentOrWhiteSpace(string targetNodeName)
        {
            return targetNodeName.Equals("WhiteSpace") || targetNodeName.Equals("SingleLineComment") || targetNodeName.Equals("SingleLineCommentCStyle") || targetNodeName.Equals("MultiLineComment");
        }

        internal XmlElement GetFirstNonWhitespaceNonCommentChildElement(XmlNode targetElement)
        {
            XmlNode xmlNode = targetElement.FirstChild;
            while (xmlNode != null)
            {
                if (xmlNode.NodeType != XmlNodeType.Element || IsCommentOrWhiteSpace(xmlNode.Name))
                {
                    xmlNode = xmlNode.NextSibling;
                    continue;
                }
                return (XmlElement)xmlNode;
            }
            return null;
        }

        internal XmlElement GetLastNonWhitespaceNonCommentChildElement(XmlNode targetElement)
        {
            XmlNode previousSibling = targetElement.LastChild;
            while (previousSibling != null)
            {
                if (previousSibling.NodeType != XmlNodeType.Element || IsCommentOrWhiteSpace(previousSibling.Name))
                {
                    previousSibling = previousSibling.PreviousSibling;
                    continue;
                }
                return (XmlElement)previousSibling;
            }
            return null;
        }

        internal void MoveToAncestorContainer(int levelsUp)
        {
            MoveToAncestorContainer(levelsUp, null);
        }

        internal void MoveToAncestorContainer(int levelsUp, string targetContainerName)
        {
            XmlElement xmlElement = CurrentContainer;
            while (levelsUp > 0)
            {
                xmlElement = (XmlElement)xmlElement.ParentNode;
                levelsUp--;
            }
            if (string.IsNullOrEmpty(targetContainerName) || xmlElement.Name.Equals(targetContainerName))
            {
                CurrentContainer = xmlElement;
                return;
            }
            throw new Exception("Ancestor node does not match expected name!");
        }
    }
}
